package game.entities.players;

import static org.lwjgl.opengl.GL11.glColor3f;
import game.ICollidable;
import game.ICollisionCheck;
import game.entities.IPlayerEntity;
import game.graphics.animations.Person;

import java.awt.Rectangle;
import java.io.Serializable;

import application.messages.commands.parameters.Coordinates2D;

public class Player implements IPlayerEntity, ICollidable, Serializable
{
	/**
	 * Constructor.
	 * Sets player to specified position. This also saves pointer to CollisionChecker to
	 * make player able to force recalculation of collisions.
	 * 
	 * @param x
	 * @param y
	 * @param collisionChecker
	 */
	public Player(int x, int y, ICollisionCheck collisionChecker)
	{
		this.setXY(x, y);
		this.lastPositionX = x;
		this.lastPositionY = y;
		
		this.collisionChecker = collisionChecker;
		
		this.velocityX = 0;
		this.velocityY = 0;
		
		this.setImunity(10000);
	}
	
	/**
	 * Redraws player.
	 */
	@Override
	public void redraw(int cameraX, int cameraY)
	{
		person.redraw(cameraX, cameraY);
	}

	/**
	 * Moves player to left (requested by user).
	 */
	@Override
	public void moveLeft()
	{
		/*positionX -= JUMP;	
			
		person.moveAtXAxis(-JUMP);*/
		
		if (!isInCollision())
		{
			velocityX -= MOVEMENT_SPEED;
		}
	}

	/**
	 * Moves player to right (requested by user).
	 */
	@Override
	public void moveRight()
	{
		/*positionX += JUMP;
		
		person.moveAtXAxis(JUMP);*/
		
		if (!isInCollision())
		{
			velocityX += MOVEMENT_SPEED;
		}
	}
	
	@Override
	public void stopMoveLeft()
	{
		this.person.animationDefaultState();
		velocityX = 0;
	}
	
	@Override
	public void stopMoveRight()
	{
		this.person.animationDefaultState();
		velocityX = 0;
	}

	/**
	 * Sets player position to specified coordinates.
	 */
	@Override
	public void setXY(int x, int y)
	{
		this.positionX = x;
		this.positionY = y;
		
		person.setPositionXY(positionX, positionY);
		this.velocityY = 0;
	}
	
	/**
	 * Jump with player (requested by user).
	 */
	@Override
	public void jump()
	{
		if (isAbleToJump())
		{
			velocityY = JUMP;
		}
	}
	
	/**
	 * Returns bounding boxes for collision checker.
	 * 
	 * @return
	 */
	public Rectangle[] getBoundingBoxes()
	{
		return person.getBoundingBoxes();
	}
	
	/**
	 * This method is called by collision checker.
	 * If it is called, object is in collision and should remove it.
	 */
	@Override
	public void removeCollision()
	{
		inCollision = true;
	
		popPosition();
		person.setPositionXY(positionX, positionY);

		if (velocityY > 2500 / 3)
		{
			if (!hasImunity() && !isImmortal())
			{
				lives -= Math.abs(velocityY / 50);
			}
		}
		
		velocityY = velocityY / 5.0f;
	}
	
	/**
	 * This method is called by collision checker.
	 * If it is called, object is not in collision and should save
	 * current coordinates, so he is able to go back on it if he
	 * is in collision later.
	 */
	@Override
	public void notInCollision()
	{
		inCollision = false;
		pushPosition();
	}
	
	/**
	 * This method is called if Player collides from down
	 * (with ground).
	 */
	@Override
	public void collidesDown()
	{
		notCollidesDownCount = 0;
		ableToJump();
	}
	
	/**
	 * This method is called if Player is not in collision
	 * with ground.
	 */
	@Override
	public void notCollidesDown()
	{
		notCollidesDownCount++;
		
		if (notCollidesDownCount > 5)
		{
			notCollidesDownCount = 0;
			notAbleToJump();
		}
	}
	
	/**
	 * Return true if player is in collision or false if it is not.
	 * 
	 * @return
	 */
	public boolean isInCollision()
	{
		return inCollision;
	}
	
	/**
	 * Returns current position of player.
	 * @return
	 */
	public Coordinates2D getPosition()
	{
		return new Coordinates2D((int) positionX, (int) positionY);
	}
	
	/**
	 * Save current position of player.
	 */
	private final void pushPosition()
	{
		this.lastPositionX = positionX;
		this.lastPositionY = positionY;
	}
	
	/**
	 * Restore previous position of player.
	 */
	private final void popPosition()
	{
		this.positionX = lastPositionX;
		this.positionY = lastPositionY;
	}
	
	/**
	 * Moves by objects based on time from last iteration.
	 * 
	 * @param deltaTime
	 */	
	public void update(long deltaTime)
	{
		int oldPositionY = (int) positionY;
		
		if (velocityX > MOVEMENT_SPEED)		velocityX = MOVEMENT_SPEED;
		if (velocityX < -MOVEMENT_SPEED)	velocityX = -MOVEMENT_SPEED;
		
		velocityY += GRAVITY * (deltaTime / 1000.0f);
		
		if (velocityY > MAXIMAL_FALL_SPEED) velocityY = MAXIMAL_FALL_SPEED;

		positionY += velocityY * (deltaTime / 1000.0f);
		person.setPositionY(positionY);
		forceCollisionsCheck();

		positionX += velocityX * (deltaTime / 1000.0f);
		person.moveAtXAxis(velocityX * (deltaTime / 1000.0f));
		//person.setPositionX(positionX);
		forceCollisionsCheck();

		//Rectangle temp = new Rectangle((int) positionX, oldPositionY, 1, (int) positionY - oldPositionY);
		
		person.setPositionXY(positionX, positionY);
		
		if (hasImunity())
		{
			if (imunityTimeSet + imunityLength <= System.currentTimeMillis())
			{
				this.imunity = false;
			}
		}
	}
	
	/**
	 * Forces collision checker to recalculate collisions.
	 */
	private void forceCollisionsCheck()
	{
		this.collisionChecker.checkCollisions();
	}
	
	/**
	 * This makes player not able to jump.
	 */
	private void notAbleToJump()
	{
		this.jumpPossible = false;
	}
	
	/**
	 * This makes player able to jump.
	 */
	private void ableToJump()
	{
		this.jumpPossible = true;
	}
	
	/**
	 * Checks if player is able to jump or not.
	 * 
	 * @return
	 * 	True: 	able to jump
	 * 	False:	not able to jump
	 */
	private boolean isAbleToJump()
	{
		return this.jumpPossible;
	}

	/**
	 * Starts shooting (animation).
	 */
	public void startShooting()
	{
		person.enableShooting();
	}
	
	/**
	 * Stops shooting (animation).
	 */
	public void stopShooting()
	{
		person.disableShooting();
	}
	
	/**
	 * Checks if object is collidable with map or not.
	 */
	@Override
	public final boolean isCollidableWithMap()
	{
		return true;
	}
	
	@Override
	public final boolean isSensitivelyDestroyable()
	{
		return false;
	}
	
	@Override
	public void destroy()
	{
		//
	}

	@Override
	public boolean hasDestroyingCharacter()
	{
		return false;
	}
	
	@Override
	public boolean isDamagable()
	{
		return true;
	}
	
	@Override
	public void damage(int lives)
	{
		if (!hasImunity() && !isImmortal())
		{
			this.lives -= lives;
		}
	}
	
	@Override
	public boolean isDestroyed()
	{
		return false;
	}
	
	public boolean isAlive()
	{
		if (lives > 0)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	
	public int getLives()
	{
		return lives;
	}

	public void setFullHealth()
	{
		this.lives = 100;
	}
	
	public void setImunity(long time)
	{
		this.imunityTimeSet	= System.currentTimeMillis();
		this.imunityLength	= time;
		this.imunity		= true;
	}
	
	public void removeImunity()
	{
		this.imunity = false;
	}
	
	public boolean hasImunity()
	{
		return imunity;
	}
	
	public void setImmortality()
	{
		this.immortality = true;
	}
	
	public void removeImmortality()
	{
		this.immortality = false;
	}
	
	public boolean isImmortal()
	{
		return immortality;
	}
	
	@Override
	public boolean isPlayerControlled()
	{
		return true;
	}
	
	@Override
	public boolean isEnemy()
	{
		return false;
	}
	
	private float		positionX;
	private float		positionY;

	private float		lastPositionX;
	private float		lastPositionY;
	
	private float		velocityX = 0;
	private float		velocityY = 0;
	
	private final float	GRAVITY					= 1024;
	private final float	MOVEMENT_SPEED			= 220;
	private final float MAXIMAL_FALL_SPEED		= 24 * GRAVITY;
	private final float	JUMP					= -600;
	
	private Person		person					= new Person();
	private boolean		inCollision				= false;
	
	private boolean		jumpPossible			= false;
	private int			notCollidesDownCount	= 0;
	
	private ICollisionCheck collisionChecker;

	private int maxHeight;
	private int minHeight;
	
	private int lives = 100;
	
	private boolean	imunity = false;
	private long	imunityTimeSet;
	private long	imunityLength;
	
	private boolean immortality;
}
